package api

import (
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"time"

	"google.golang.org/grpc"

	"github.com/massnetorg/mass-core/blockchain"
	"github.com/massnetorg/mass-core/logging"
	"github.com/massnetorg/mass-core/massutil"
	"github.com/massnetorg/mass-core/netsync"

	pb "massnet.org/mass-wallet/api/proto"
	"massnet.org/mass-wallet/config"
	"massnet.org/mass-wallet/masswallet"
)

const (
	maxMsgSize        = 1024 * 1024 * 64
	GRPCListenAddress = "127.0.0.1"
)

type MassNode interface {
	TxMemPool() *blockchain.TxPool
	SyncManager() *netsync.SyncManager
	Blockchain() *blockchain.Blockchain
}

type APIServer struct {
	rpcServer  *grpc.Server
	node       MassNode
	config     *config.Config
	massWallet *masswallet.WalletManager
	quitClient func()
}

func NewAPIServer(node MassNode, masswallet *masswallet.WalletManager, quitClient func(), config *config.Config) (*APIServer, error) {

	// set the size for receive Msg
	opts := []grpc.ServerOption{
		grpc.MaxRecvMsgSize(maxMsgSize),
		grpc.MaxSendMsgSize(maxMsgSize),
	}
	s := grpc.NewServer(opts...)
	srv := &APIServer{
		rpcServer:  s,
		node:       node,
		config:     config,
		massWallet: masswallet,
		quitClient: quitClient,
	}
	pb.RegisterApiServiceServer(s, srv)
	// Register reflection service on gRPC server.
	// reflection.Register(s)
	return srv, nil
}

func (s *APIServer) Start() error {
	address := fmt.Sprintf("%s%s%s", GRPCListenAddress, ":", s.config.Wallet.API.GRPCPort)
	listen, err := net.Listen("tcp", address)
	if err != nil {
		logging.CPrint(logging.ERROR, "failed to start grpc server", logging.LogFormat{"port": s.config.Wallet.API.GRPCPort, "error": err})
		return err
	}
	go s.rpcServer.Serve(listen)
	logging.CPrint(logging.INFO, "grpc server started", logging.LogFormat{"port": s.config.Wallet.API.GRPCPort})
	return nil
}

func (s *APIServer) Stop() {
	s.rpcServer.Stop()
	logging.CPrint(logging.INFO, "APIServer stopped", logging.LogFormat{})
}

func (s *APIServer) RunGateway() {
	go func() {
		if err := Run(s.config); err != nil {
			logging.CPrint(logging.ERROR, "failed to start grpc-gateway", logging.LogFormat{"port": s.config.Wallet.API.HttpPort, "error": err})
			return
		}
		logging.CPrint(logging.INFO, "grpc-gateway start", logging.LogFormat{"port": s.config.Wallet.API.HttpPort})
	}()
}

func openRPCKeyPair(cfg *config.Config) (tls.Certificate, error) {
	_, e := os.Stat(cfg.Wallet.API.RpcKey)
	if os.IsNotExist(e) {
		return generateRPCKeyPair(cfg)
	}
	return tls.LoadX509KeyPair(cfg.Wallet.API.RpcCert, cfg.Wallet.API.RpcKey)
}

func generateRPCKeyPair(cfg *config.Config) (tls.Certificate, error) {
	logging.CPrint(logging.INFO, "Generating TLS certificates...", logging.LogFormat{
		"cert": cfg.Wallet.API.RpcCert,
		"key":  cfg.Wallet.API.RpcKey,
	})

	// Create directories for cert and key files if they do not yet exist.
	certDir, _ := filepath.Split(cfg.Wallet.API.RpcCert)
	keyDir, _ := filepath.Split(cfg.Wallet.API.RpcKey)
	err := os.MkdirAll(certDir, 0700)
	if err != nil {
		return tls.Certificate{}, err
	}
	err = os.MkdirAll(keyDir, 0700)
	if err != nil {
		return tls.Certificate{}, err
	}

	// Generate cert pair.
	org := "masswallet autogenerated cert"
	validUntil := time.Now().Add(time.Hour * 24 * 365 * 10)
	cert, key, err := massutil.NewTLSCertPair(org, validUntil, nil)
	if err != nil {
		return tls.Certificate{}, err
	}
	keyPair, err := tls.X509KeyPair(cert, key)
	if err != nil {
		return tls.Certificate{}, err
	}

	// Write cert and (potentially) the key files.
	err = ioutil.WriteFile(cfg.Wallet.API.RpcCert, cert, 0700)
	if err != nil {
		return tls.Certificate{}, err
	}
	err = ioutil.WriteFile(cfg.Wallet.API.RpcKey, key, 0700)
	if err != nil {
		rmErr := os.Remove(cfg.Wallet.API.RpcCert)
		if rmErr != nil {
			logging.CPrint(logging.WARN, "Cannot remove written certificates", logging.LogFormat{"error": rmErr})
		}
		return tls.Certificate{}, err
	}
	logging.CPrint(logging.INFO, "Done generating TLS certificates", logging.LogFormat{})
	return keyPair, nil
}
